﻿using System;
using System.Collections.Generic;
using System.IO;
using OscarClover.Exception;
using OscarClover.Packets;
using OscarClover.Packets.SignOff;
using OscarClover.Packets.SignOn;
using OscarClover.Packets.SnacData;

namespace OscarClover
{
    public class OscarFormatter
    {
        private BinaryReader _reader;
        private BinaryWriter _writer;
        private MemoryStream _cache;

        public Flap Deserialize(Stream serializationStream)
        {
            Flap result = null;
            _reader = new BinaryReader(serializationStream);
            if (_reader.ReadByte().Equals(0x2A)) //Flap header
            {
                var channel = _reader.ReadByte();
                if (channel.Equals(0x02)) //Snac Channel
                {
                    if ((_reader.ReadByte() * 256 + _reader.ReadByte()).Equals(++Session.RecievedSequence))
                    //Session check
                    {
                        var len = (ushort)(_reader.ReadByte() * 256 + _reader.ReadByte());
                        var family = (ushort)(_reader.ReadByte() * 256 + _reader.ReadByte());
                        var subfamily = (ushort)(_reader.ReadByte() * 256 + _reader.ReadByte());
                        var flags = (ushort)(_reader.ReadByte() * 256 + _reader.ReadByte());
                        var reqId =
                            (uint)
                            (_reader.ReadByte() * 65535 + _reader.ReadByte() * 4096 + _reader.ReadByte() * 256 +
                             _reader.ReadByte());

                        if (family.Equals(0x01)) //AIM Generic
                        {
                            if (subfamily.Equals(0x03)) // Server Ready
                            {
                                result = new SrvFamilies(len);

                                for (var i = 0; i < result.Len - 10; i++) //
                                {
                                    if (i % 2 == 0)
                                    {
                                        _reader.ReadByte();
                                    }
                                    else
                                    {
                                        (result as SrvFamilies).Services.Add(_reader.ReadByte());
                                    }
                                }
                            }
                            else if (subfamily.Equals(0x07)) //Rate Info TODO: Complete this,if needed
                            {
                                _reader.ReadByte();
                                result = new SrvRateLimitInfo(_reader.ReadByte());
                                _reader.ReadBytes(len - 12);
                            }
                            else if (subfamily.Equals(0x0F)) // Self Info Reply TODO: Complete this,if needed
                            {
                                result = new SrvJust4Read(family, subfamily);
                                _reader.ReadBytes(len - 10);
                            }
                            else if (subfamily.Equals(0x13)) //Message Of The Day
                            {
                                result = new SrvJust4Read(family, subfamily);
                                _reader.ReadBytes(len - 10);
                            }
                            else if (subfamily.Equals(0x15)) // Well Know URL
                            {
                                result = new SrvJust4Read(family, subfamily);
                                _reader.ReadBytes(len - 10);
                            }
                            else if (subfamily.Equals(0x18)) // Server Family Version Ack
                            {
                                result = new SrvFamiliesVersions(len);
                                for (var i = 0; i < (result.Len - 10) / 4; i++)
                                {
                                    _reader.ReadByte();
                                    var curFamily = _reader.ReadByte();
                                    _reader.ReadByte();
                                    var curVersion = _reader.ReadByte();
                                    (result as SrvFamiliesVersions).Families.Add(curFamily, curVersion);
                                }
                            }
                        }
                        else if (family.Equals(0x02)) // Location
                        {
                            if (subfamily.Equals(0x03)) // Location Rights Reply TODO: Complete this,if needed
                            {
                                result = new SrvJust4Read(family, subfamily);
                                _reader.ReadBytes(len - 10);
                            }
                        }
                        else if (family.Equals(0x03)) // Buddy List Management
                        {
                            if (subfamily.Equals(0x03)) // Buddy List Rights Reply TODO: Complete this,if needed
                            {
                                result = new SrvJust4Read(family, subfamily);
                                _reader.ReadBytes(len - 10);
                            }
                        }
                        else if (family.Equals(0x04)) // Messaging
                        {
                            if (subfamily.Equals(0x05)) // Messaging Parameter Info TODO: Complete this,if needed
                            {
                                result = new SrvJust4Read(family, subfamily);
                                _reader.ReadBytes(len - 10);
                            }
                        }
                        else if (family.Equals(0x09)) // BOS
                        {
                            if (subfamily.Equals(0x03)) // BOS Rights Reply TODO: Complete this,if needed
                            {
                                result = new SrvJust4Read(family, subfamily);
                                _reader.ReadBytes(len - 10);
                            }
                        }
                        else if (family.Equals(0x13)) // SSI
                        {
                            if (subfamily.Equals(0x03)) // SSI Rights Reply TODO: Complete this,if needed
                            {
                                result = new SrvJust4Read(family, subfamily);
                                _reader.ReadBytes(len - 10);
                            }
                            else if (subfamily.Equals(0x06)) // List TODO:THIS MUST WORK
                            {
                                result = new SrvJust4Read(family, subfamily);
                                _reader.ReadBytes(len - 10);
                            }
                        }

                    }
                }
                else if (channel.Equals(0x04)) // Close Connection Channel
                {
                    if ((_reader.ReadByte() * 256 + _reader.ReadByte()).Equals(++Session.RecievedSequence))
                    //Session check
                    {
                        var len = (ushort)(_reader.ReadByte() * 256 + _reader.ReadByte());
                        var tlvs = new TlvList();
                        tlvs.AddRange(GetTlvList(_reader.ReadBytes(len)));
                        if (tlvs[0x06] != null)
                        {
                            result = new SrvCookie(len, tlvs);
                        }
                    }
                }
                else if (channel.Equals(0x01)) //Connection Channel
                {
                    result = new Connect();
                    Session.RecievedSequence = (ushort)(256 * _reader.ReadByte() + _reader.ReadByte());
                    _reader.ReadBytes(6);
                }
                if (result == null)
                {
                    throw new DeserializeException("Flap deserialization not found");
                }
            }
            
            return result;
        }

        public void Serialize(Stream serializationStream, Connect packet)
        {
            var bytes = new byte[6 + (packet.Len != 0 ? (int)packet.Len : 0)];
            FlapSerialize(bytes, packet);
            Array.Copy(packet.DataArray, 0, bytes, 6, packet.DataArray.Length);
            _writer = new BinaryWriter(serializationStream);
            _writer.Write(bytes);
            _writer.Flush();
        }

        public void Serialize(Stream serializationStream, CloseConnection packet)
        {
            var bytes = new byte[6];
            FlapSerialize(bytes, packet);
            _writer = new BinaryWriter(serializationStream);
            _writer.Write(bytes);
            _writer.Flush();
        }

        public void Serialize(Stream serializationStream, CliSignOn packet)
        {
            var bytes = new byte[6 + (packet.Len != 0 ? (int)packet.Len : 0)];
            FlapSerialize(bytes, packet);
            if (packet.DataArray.Length != 0)
            {
                Array.Copy(packet.DataArray, 0, bytes, 6, packet.DataArray.Length);
            }
            Array.Copy(ParseTlvList(packet.TlvList, packet.Len - packet.DataArray.Length), 0, bytes,
                       6 + packet.DataArray.Length, packet.Len - packet.DataArray.Length);
            _writer = new BinaryWriter(serializationStream);
            _writer.Write(bytes);
            _writer.Flush();
        }

        public void Serialize(Stream serializationStream, CliFamiliesVersions packet)
        {
            var bytes = new byte[16 + packet.FamiliesVersion.Count * 4];
            FlapSerialize(bytes, packet);
            SnacSerialize(bytes, packet);
            var current = 16;
            foreach (var familiesVersion in packet.FamiliesVersion)
            {
                bytes[current] = 0;
                bytes[++current] = familiesVersion.Key;
                bytes[++current] = 0;
                bytes[++current] = familiesVersion.Value;
                ++current;
            }
            _writer = new BinaryWriter(serializationStream);
            _writer.Write(bytes);
            _writer.Flush();
        }

        public void Serialize(Stream serializationStream, CliRequest packet)
        {
            var bytes = new byte[16];
            FlapSerialize(bytes, packet);
            SnacSerialize(bytes, packet);
            _writer = new BinaryWriter(serializationStream);
            _writer.Write(bytes);
            _writer.Flush();
        }

        public void Serialize(Stream serializationStream, CliRatesAck packet)
        {
            var bytes = new byte[16 + packet.ClassCount * 2];
            FlapSerialize(bytes, packet);
            SnacSerialize(bytes, packet);
            var current = 16;
            for (var i = 0; i < packet.ClassCount; i++)
            {
                bytes[current] = 0x00;
                bytes[++current] = (byte)(i + 1);
                ++current;
            }
            _writer = new BinaryWriter(serializationStream);
            _writer.Write(bytes);
            _writer.Flush();
        }

        public void Serialize(Stream serializationStream, CliSetLocationInfo packet)
        {
            var bytes = new byte[20 + 16*packet.Capabilities.Count];
            FlapSerialize(bytes, packet);
            SnacSerialize(bytes, packet);
            bytes[16] = 0x00;
            bytes[17] = 0x05; //Tlv value emulation
            bytes[18] = (byte) (((packet.Capabilities.Count * 16) >> 8) & 0xff);
            bytes[19] = (byte) ((packet.Capabilities.Count * 16) & 0xff);
            var current = 20;
            foreach (var capability in packet.Capabilities)
            {
                Array.Copy(capability,0,bytes,current,capability.Length);
                current += 16;
            }
            _writer = new BinaryWriter(serializationStream);
            _writer.Write(bytes);
            _writer.Flush();
        }

        public void Serialize(Stream serializationStream, CliSetStatus packet)
        {
            var bytes = new byte[6+packet.Len];
            FlapSerialize(bytes,packet);
            SnacSerialize(bytes,packet);
            Array.Copy(ParseTlvList(packet.TlvList,packet.Len-10),0,bytes,16,packet.Len -10);
            _writer = new BinaryWriter(serializationStream);
            _writer.Write(bytes);
            _writer.Flush();
        }

        public void Serialize(Stream serializationStream, CliReady packet)
        {
            var bytes = new byte[6 + packet.Len];
            FlapSerialize(bytes,packet);
            SnacSerialize(bytes,packet);
            var current = 16;
            foreach (var familiesVersion in packet.FamiliesVersion)
            {
                bytes[current] = 0x00;
                bytes[++current] = familiesVersion.Key;
                bytes[++current] = 0x00;
                bytes[++current] = familiesVersion.Value;
                bytes[++current] = (byte) ((packet.DllVersion >> 24) & 0xff);
                bytes[++current] = (byte) ((packet.DllVersion >> 16) & 0xff);
                bytes[++current] = (byte) ((packet.DllVersion >> 8) & 0xff);
                bytes[++current] = (byte) (packet.DllVersion & 0xff);
                ++current;
            }
            _writer = new BinaryWriter(serializationStream);
            _writer.Write(bytes);
            _writer.Flush();
        }

        public void Cache(CliRequest packet)
        {
            if (_cache == null)
            {
                _cache = new MemoryStream();
            }
            Serialize(_cache, packet);
        }

        public void FlushCache(Stream serializationStream)
        {
            _writer = new BinaryWriter(serializationStream);
            _writer.Write(_cache.ToArray());
            _writer.Flush();
            _cache.Close();
        }

        private static byte[] ParseTlvList(IEnumerable<Tlv> tlvList, long len)
        {
            var result = new byte[len];
            var position = 0;
            foreach (var tlv in tlvList)
            {
                Array.Copy(tlv.GetBytes(), 0, result, position, tlv.Len + 4);
                position += tlv.Len + 4;
            }
            return result;
        }

        public TlvList GetTlvList(byte[] data)
        {
            var result = new TlvList();
            for (int i = 0; i < data.Length; )
            {
                var tag = (ushort)(data[i] * 256 + data[i + 1]);
                var len = (ushort)(data[i + 2] * 256 + data[i + 3]);
                var value = new byte[len];
                for (int j = 0; j < len; ++j)
                    value[j] = data[i + 4 + j];
                result.Add(new Tlv(tag, value));
                i = i + 4 + len;
            }
            return result;
        }

        private static void FlapSerialize(byte[] bytes, Flap packet)
        {
            bytes[0] = 0x2A;
            bytes[1] = (byte)packet.FrameType;
            bytes[2] = (byte)(Session.Sequence >> 8 & 0xff);
            bytes[3] = (byte)(Session.Sequence & 0xff);
            bytes[4] = (byte)(packet.Len >> 8 & 0xff);
            bytes[5] = (byte)(packet.Len & 0xff);
            Session.Sequence++;
        }

        private static void SnacSerialize(byte[] bytes, Snac packet)
        {
            bytes[6] = (byte)(packet.Family >> 8 & 0xff);
            bytes[7] = (byte)(packet.Family & 0xff);
            bytes[8] = (byte)(packet.SubType >> 8 & 0xff);
            bytes[9] = (byte)(packet.SubType & 0xff);
            bytes[10] = (byte)(packet.Flags >> 8 & 0xff);
            bytes[11] = (byte)(packet.Flags & 0xff);
            bytes[12] = (byte)(packet.ReqId >> 24 & 0xff);
            bytes[13] = (byte)(packet.ReqId >> 16 & 0xff);
            bytes[14] = (byte)(packet.ReqId >> 8 & 0xff);
            bytes[15] = (byte)(packet.ReqId & 0xff);
        }

        /* public object Deserialize(Stream serializationStream)
        {
            Flap result = null;
            _reader = new BinaryReader(serializationStream);
            if (_reader.ReadByte().Equals(0x2A))
            {
                    result = new Flap((FrameTypes)_reader.ReadByte());
                    Flap.Sequence =
                        (ushort) (_reader.ReadByte()*256 + _reader.ReadByte());
                    var totalLen = (ushort) (_reader.ReadByte()*256 + _reader.ReadByte());
                    for (int current = 0; current < totalLen;)
                    {
                        var tag = (ushort) (_reader.ReadByte()*256 + _reader.ReadByte());
                        var len = (ushort) (_reader.ReadByte()*256 + _reader.ReadByte());
                        var value = new byte[len];
                        for (int j = 0; j < len; j++)
                        {
                            value[j] = _reader.ReadByte();
                        }
                        result.AddTlv(new Tlv(tag, value));
                        current += len + 4;
                    }
                }
            return result;
        }

        public void Serialize(Stream serializationStream, object graph)
        {
            if (graph is Flap)
            {
                var flapFormatter = (Flap) graph;
                var bytes = new byte[4 + (flapFormatter.Len != 0 ? 2 + flapFormatter.Len : 0)];
                bytes[0] = 0x2A;
                bytes[1] = ((byte) flapFormatter.FrameType);
                bytes[2] = ((byte) ((byte) Flap.Sequence >> 8 & 0xff));
                bytes[3] = ((byte) ((byte) Flap.Sequence & 0xff));
                bytes[4] = (byte) ((byte) flapFormatter.Len >> 8 & 0xff);
                bytes[5] = ((byte) ((byte) flapFormatter.Len & 0xff));
                if (flapFormatter.AdditionalDataArray != null)
                {
                    Array.Copy(flapFormatter.AdditionalDataArray, 0, bytes, 6, flapFormatter.AdditionalDataArray.Length);
                }
                int position = (flapFormatter.AdditionalDataArray != null
                                    ? 6 + flapFormatter.AdditionalDataArray.Length
                                    : 6);
                for (int i = 0; i < flapFormatter.TlvList.Count; i++)
                {
                    Array.Copy(flapFormatter.TlvList[i].GetBytes(), 0, bytes, position
                               , flapFormatter.TlvList[i].Len + 4);
                    position += flapFormatter.TlvList[i].Len + 4;
                }
                _writer = new BinaryWriter(serializationStream);
                _writer.Write(bytes);
                _writer.Flush();
            }
        }*/
    }
}

/*
 
 */
